﻿using System;
using System.Threading;

namespace RockBus.Threading
{
    public abstract class AsyncResultBase<TRequest> : IAsyncResult, IDisposable
        where TRequest : class
    {
        private AsyncResultBase(TRequest request, AsyncCallback callback, object state)
        {
            this.m_request = request;
            this.m_asyncCallback = callback;
            this.m_asyncState = state;
        }

        protected AsyncResultBase(Exception exception, TRequest request, AsyncCallback callback, object state)
            : this(request, callback, state)
        {
            this.Exception = exception;
            this.CompletedSynchronously = true;
        }

        protected AsyncResultBase(Func<TRequest, AsyncCallback, object, IAsyncResult> beginFunc, TRequest request, AsyncCallback callback, object state)
            : this(request, callback, state)
        {
            this.m_beginFunc = beginFunc;
        }

        protected abstract void End();

        private readonly object _manualResetEventLockObject = new object();
        private ManualResetEvent _manualResetEvent;

        public ManualResetEvent ManualResetEvent
        {
            get
            {
                if (this._manualResetEvent == null)
                {
                    lock (this._manualResetEventLockObject)
                    {
                        if (this._manualResetEvent == null)
                        {
                            this._manualResetEvent = new ManualResetEvent(false);
                        }
                    }
                }

                return this._manualResetEvent;
            }
        }

        protected IAsyncResult Begin()
        {
            return this.m_beginFunc(this.Request, this.Callback, null);
        }

        protected void Callback(IAsyncResult asyncResult)
        {
            End();
            Complete();
        }

        protected void Complete()
        {
            this._isCompleted = true;
            this.ManualResetEvent.Set();
            this.m_asyncCallback(this);
        }

        protected readonly TRequest m_request;

        public TRequest Request
        {
            get { return m_request; }
        }

        protected readonly AsyncCallback m_asyncCallback;

        public AsyncCallback AsyncCallback
        {
            get { return this.m_asyncCallback; }
        }

        protected readonly object m_asyncState;

        public object AsyncState
        {
            get { return this.m_asyncState; }
        }

        protected IAsyncResult m_wrappedAsyncResult;

        public IAsyncResult WrappedAsyncResult
        {
            get { return m_wrappedAsyncResult; }
        }

        protected readonly Func<TRequest, AsyncCallback, object, IAsyncResult> m_beginFunc;

        public Func<TRequest, AsyncCallback, object, IAsyncResult> BeginFunc
        {
            get { return m_beginFunc; }
        }

        public WaitHandle AsyncWaitHandle
        {
            get { return this.ManualResetEvent; }
        }

        public bool CompletedSynchronously { get; set; }

        private bool _isCompleted = false;

        public bool IsCompleted
        {
            get { return this._isCompleted; }
        }

        protected bool m_endCalled;

        public bool HasCallback
        {
            get { return (null != this.m_asyncCallback); }
        }

        private Exception _exception;

        protected Exception Exception
        {
            get { return _exception; }
            set { _exception = value; }
        }

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private bool _disposed;

        private void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    if (null != this._manualResetEvent)
                    {
                        this._manualResetEvent.Close();
                        this._manualResetEvent = null;
                    }
                }
            }
            _disposed = true;
        }

        #endregion IDisposable Members
    }

    public class AsyncResultVoid<TRequest> : AsyncResultBase<TRequest>
        where TRequest : class
    {
        public static void End(IAsyncResult asyncResult)
        {
            AsyncResultVoid<TRequest> ar = asyncResult as AsyncResultVoid<TRequest>;
            ar.End();

            if (null != ar.Exception)
            {
                throw ar.Exception;
            }
        }

        public AsyncResultVoid(Exception exception, TRequest request, AsyncCallback callback, object state)
            : base(exception, request, callback, state)
        {
            this.End();
            this.Complete();
        }

        public AsyncResultVoid(Func<TRequest, AsyncCallback, object, IAsyncResult> beginFunc, TRequest request, AsyncCallback callback, object state, Action<IAsyncResult> endAction)
            : base(beginFunc, request, callback, state)
        {
            this._endAction = endAction;
            this.m_wrappedAsyncResult = Begin();
        }

        private readonly Action<IAsyncResult> _endAction;

        public Action<IAsyncResult> EndAction
        {
            get { return _endAction; }
        }

        private readonly object _endActionLockObject = new object();

        protected override void End()
        {
            if (!this.m_endCalled)
            {
                lock (this._endActionLockObject)
                {
                    if (!this.m_endCalled)
                    {
                        if ((null != this.WrappedAsyncResult) && !this.WrappedAsyncResult.IsCompleted)
                        {
                            this.WrappedAsyncResult.AsyncWaitHandle.WaitOne();
                        }

                        try
                        {
                            if (null != this.EndAction)
                            {
                                this.EndAction(this.WrappedAsyncResult);
                            }
                        }
                        catch (Exception exc)
                        {
                            this.Exception = exc;
                        }
                        this.m_endCalled = true;
                    }
                }
            }
        }
    }

    public class AsyncResult<TRequest, TResponse> : AsyncResultBase<TRequest>
        where TRequest : class
        where TResponse : class
    {
        public static TResponse End(IAsyncResult asyncResult)
        {
            AsyncResult<TRequest, TResponse> ar = asyncResult as AsyncResult<TRequest, TResponse>;
            ar.End();

            if (null != ar.Exception)
            {
                throw ar.Exception;
            }

            return ar.Response;
        }

        public AsyncResult(Exception exception, TRequest request, AsyncCallback callback, object state)
            : base(exception, request, callback, state)
        {
            this.End();
            this.Complete();
        }

        public AsyncResult(Func<TRequest, AsyncCallback, object, IAsyncResult> beginFunc, TRequest request, AsyncCallback callback, object state, Func<IAsyncResult, TResponse> endFunc)
            : base(beginFunc, request, callback, state)
        {
            this._endFunc = endFunc;
            this.m_wrappedAsyncResult = Begin();
        }

        private readonly object _endActionLockObject = new object();

        protected override void End()
        {
            if (!this.m_endCalled)
            {
                lock (this._endActionLockObject)
                {
                    if (!this.m_endCalled)
                    {
                        if ((null != this.WrappedAsyncResult) && !this.WrappedAsyncResult.IsCompleted)
                        {
                            this.WrappedAsyncResult.AsyncWaitHandle.WaitOne();
                        }

                        try
                        {
                            if (null != this.EndFunc)
                            {
                                this.Response = this.EndFunc(this.WrappedAsyncResult);
                            }
                        }
                        catch (Exception exc)
                        {
                            this.Exception = exc;
                        }
                        this.m_endCalled = true;
                    }
                }
            }
        }

        private readonly Func<IAsyncResult, TResponse> _endFunc;

        public Func<IAsyncResult, TResponse> EndFunc
        {
            get { return _endFunc; }
        }

        protected TResponse m_response;

        public TResponse Response
        {
            get { return m_response; }
            private set { m_response = value; }
        }
    }
}